package com.starlink.common.log.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.starlink.common.log.ApplicationLogContext;
import com.starlink.common.log.configuration.CommonLogConfiguration;
import com.starlink.common.log.dto.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;

/**
 * 通用入参出参以及异常日志
 *
 * @author zhaosq
 * @since 2021-03-26
 */
@Aspect
@Order(2)
@Slf4j
public class CommonLogAspect {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    private static final ThreadLocal<Long> startTime = new ThreadLocal<>();
    private static final ThreadLocal<StringBuilder> sb = new ThreadLocal<>();


    @Autowired
    private ApplicationLogContext applicationLogContext;
    @Autowired
    private CommonLogConfiguration  commonLogConfiguration;

    @Autowired
    private ObjectMapper objectMapper;

    @Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
    public void restController() {

    }

    @Pointcut("@within(org.springframework.stereotype.Controller)")
    public void controller() {

    }


    @Before("controller()||restController()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        final UserInfo userInfo = applicationLogContext.getUserInfo();;
        StringBuilder builder = new StringBuilder();
        startTime.set(System.currentTimeMillis());
        sb.set(builder);
        // 记录下请求内容
        builder.append("--------------------begin new------------------------").append(LINE_SEPARATOR);
        builder.append("url : ").append(request.getRequestURL().toString()).append(LINE_SEPARATOR);
        builder.append("request : ").append(request.getMethod()).append(LINE_SEPARATOR);
        builder.append("class method : ").append(joinPoint.getSignature().getDeclaringTypeName()).append(".").append(joinPoint.getSignature().getName()).append(LINE_SEPARATOR);
        //考虑rpc调用采购无法获取用户信息
        if (Objects.nonNull(userInfo)) {
            builder.append("current user : ").append(String.format("{userId:%d,userName:%s}", userInfo.getId(), userInfo.getName())).append(LINE_SEPARATOR);
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] objects = joinPoint.getArgs();
        Parameter[] parameters = method.getParameters();

        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            if (p.getType().equals(HttpServletRequest.class)) {
                continue;
            }
            if (p.getType().equals(HttpServletResponse.class)) {
                continue;
            }
            if ("bytes".equalsIgnoreCase(p.getName())) {
                continue;
            }
            if (objects[i] != null) {
                try {
                    builder.append("args : ").append(objectMapper.writeValueAsString(ImmutableMap.of(p.getName(), objects[i]))).append(LINE_SEPARATOR);
                } catch (Exception e) {
                    log.error("json output args {} error {} ", p.getName(), e);
                }
            }
        }
        log.info(builder.toString());

        builderLoggerMDC(request, joinPoint, userInfo);
    }

    private void builderLoggerMDC(HttpServletRequest request, JoinPoint joinPoint, UserInfo userInfo) {
        try {
            if (Objects.nonNull(userInfo)) {
                MDC.put("current user", String.format("{userId:%d,userName:%s}", userInfo.getId(), userInfo.getName()));
            }
            if (!StringUtils.isEmpty(applicationLogContext.getApplicationName())) {
                MDC.put("serverName", applicationLogContext.getApplicationName());
            }
            MDC.put("remoteIP", request.getRemoteAddr());
            MDC.put("requestParams", objectMapper.writeValueAsString(Arrays.toString(joinPoint.getArgs())));
            MDC.put("uri", request.getRequestURI());
            MDC.put("queryString", request.getQueryString());
            MDC.put("localIP", request.getLocalAddr());

            //后续可设计带有语义的traceId
            MDC.put("traceId", UUID.randomUUID().toString().replace("-", ""));

        } catch (Exception e) {
            log.error("builderLoggerMDC error={}", e);
        }
    }

    @AfterReturning(pointcut = "controller()||restController()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求，返回内容
        StringBuilder builder = new StringBuilder();
        if(commonLogConfiguration.getHasResponsePrint()){
            builder.append("response : ").append(objectMapper.writeValueAsString(ret)).append(LINE_SEPARATOR);
        }
        builder.append("time : ").append(System.currentTimeMillis() - startTime.get()).append("ms").append(LINE_SEPARATOR);
        builder.append("---------------------end-----------------------").append(LINE_SEPARATOR);
        log.info(builder.toString());

        MDC.put("code", HttpStatus.OK.value() + "");
    }

    @AfterThrowing(pointcut = "controller()||restController()", throwing = "exp")
    public void doAfterThrowing(Exception exp) throws Throwable {
        // 处理完请求，返回内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        StringBuilder builder = new StringBuilder();

        builder.append("exception : ").append(ExceptionUtils.getStackTrace(exp)).append(LINE_SEPARATOR);
        builder.append("time : ").append(System.currentTimeMillis() - startTime.get()).append("ms").append(LINE_SEPARATOR);
        builder.append("---------------------end-----------------------").append(LINE_SEPARATOR);

        log.error(builder.toString());
    }


}
